package com.bobo.service.impl;

import com.bobo.context.BaseContext;
import com.bobo.entity.Voucher;
import com.bobo.entity.VoucherOrder;
import com.bobo.mapper.VoucherMapper;
import com.bobo.result.Result;
import com.bobo.service.VoucherService;
import com.bobo.utils.RedisWorker;
import com.bobo.dto.VoucherPageQueryDTO;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import lombok.extern.slf4j.Slf4j;

import org.redisson.Redisson;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;


import java.lang.reflect.Proxy;
import java.util.Collections;
import java.util.List;;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

@Service
@Slf4j
public class VoucherServiceImpl implements VoucherService {
    @Autowired
    private StringRedisTemplate stringRedisTemplate;
    @Autowired
    private VoucherMapper voucherMapper;
    @Autowired
    private RedisWorker redisWorker;
    @Autowired
    private RabbitTemplate rabbitTemplate;

    @Autowired
    private RedissonClient redisson;


    private static final DefaultRedisScript<Long> SECKILL_SCRIPT;
    static {
        SECKILL_SCRIPT=new DefaultRedisScript<>();
        SECKILL_SCRIPT.setLocation(new ClassPathResource("voucher.lua"));
        SECKILL_SCRIPT.setResultType(Long.class);
    }
    private static final DefaultRedisScript<Long> ROLLBACK_SCRIPT ;
    static {
        ROLLBACK_SCRIPT=new DefaultRedisScript<>();
        ROLLBACK_SCRIPT.setLocation(new ClassPathResource("rollbackVoucher.lua"));
        ROLLBACK_SCRIPT.setResultType(Long.class);
    }

    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;


    /**
     * 购买优惠券
     * @param id
     * @return
     */
    @Override
    public Result<Long> getVoucher(Long id) throws InterruptedException {
        //1.获取用户ID
        Long userId = BaseContext.getCurrentId();
        //1.2生成orderId
        Long orderId = redisWorker.nextId("voucher:consumer:" + id.toString());
        //2.判断用户是否有购买资格
        Long result = stringRedisTemplate.execute(
                SECKILL_SCRIPT,
                Collections.emptyList(),
                id.toString(),userId.toString(),String.valueOf(orderId)
        );
        int r = result.intValue();
        if(r!=0){
            return Result.error(r==1?"库存不足":"不能重复下单");
        }
        //3.1封装消息
        VoucherOrder voucherOrder = new VoucherOrder();
        voucherOrder.setOrderId(orderId);
        voucherOrder.setUserId(userId);
        voucherOrder.setVoucherId(id);
        voucherOrder.setStatus(0);
        //3.2发送消息       这里要保证原子性，我舍弃了0.4秒的用户体验换取原子性，如果消息发送失败就重试两次，再失败直接回滚并返回失败
        for(int i = 0 ;i<3;i++) {
            try {
                //正常情况发送消息，直接return订单id
                rabbitTemplate.convertAndSend("voucher.direct",
                        "voucher.order.create",
                        voucherOrder);
                return Result.success(orderId);
            } catch (AmqpException e) {
                //非正常情况，重试两次，再失败就回滚并返回给用户错误
                if(i==2){
                    stringRedisTemplate.execute(
                            ROLLBACK_SCRIPT,
                            Collections.emptyList(),
                            id.toString(),userId.toString(),String.valueOf(orderId)
                    );
                    return Result.error("服务器开小差了...");
                }
                Thread.sleep(200);
            }
        }
        return Result.error("此return不可达，仅为代码完善性");
    }

    /**
     * 添加优惠券
     * @param voucher
     */
    @Transactional
    public void addVoucher(Voucher voucher) {
        //1.获取优惠券id和库存插入到redis
        Integer stock = voucher.getStock();
        //2.修改数据库
        voucherMapper.addVoucher(voucher);
        TransactionAspectSupport.currentTransactionStatus().flush();
        Long id = voucher.getId();
        log.info("生成的主键是：{}",voucher.getId());
        stringRedisTemplate.opsForValue().set("seckill:stock:"+id.toString(),stock.toString());
    }
    /**
     * 分页查询优惠券
     * @param voucherPageQueryDTO
     * @return
     */
    @Override
    public List<Voucher> getVoucherByPage(VoucherPageQueryDTO voucherPageQueryDTO) {
        //1.获取分页信息
        PageHelper.startPage(voucherPageQueryDTO.getPage(),voucherPageQueryDTO.getPageSize());
        //2.分页查询
        Page<Voucher> page = voucherMapper.pageQuery(voucherPageQueryDTO);
        long total = page.getTotal();
        List<Voucher> records=page.getResult();
        //3.封装返回
        return records;
    }
    /**
     * 优惠券修改状态
     * @param status
     * @param id
     * @return
     */
    @Override
    public void updateVoucherStatus(Integer status,Long id) {
        //1.判断优惠券是否存在
        Integer voucherStatus = voucherMapper.selectVoucherById(id);
        //2.不存在
        if(voucherStatus == null) {
            return ;
        }
        //3.存在
        //3.1状态是否相等
        if(status.equals(voucherStatus)) {
            return;
        }
        if(status.equals(1)) {
            //4.如果是停用删除redis的优惠券数据
            stringRedisTemplate.opsForValue().getAndDelete("seckill:stock:"+id.toString());
        }
        if(status.equals(0)) {
            //5.如果是启用优惠券那就创建redis数据
            Integer stock = voucherMapper.getStockById(id);
            stringRedisTemplate.opsForValue().set("seckill:stock:"+id.toString(),stock.toString());
        }

        voucherMapper.updateVoucherStatus(id,status);
    }

    /**
     * 修改优惠券
     * @param voucher
     */
    @Override
    @Transactional
    public void updateVoucher(Voucher voucher) {
        //1.获取优惠券id
        Long id = voucher.getId();
        //2.删除旧的优惠券
        stringRedisTemplate.opsForValue().getAndDelete("seckill:stock:"+id.toString());
        voucherMapper.delVoucherById(id);
        //3.添加新状态优惠券
        Integer stock = voucher.getStock();
        stringRedisTemplate.opsForValue().set("seckill:stock:"+id.toString(),stock.toString());
        voucherMapper.UpdateVoucher(voucher);
    }

    /**
     * 删除优惠券
     * @param id
     */
    @Override
    public void deleteVoucher(Long id) {
        stringRedisTemplate.opsForValue().getAndDelete("seckill:stock:"+id.toString());
        voucherMapper.delVoucherById(id);
    }

    /**
     * 根据订单ID查询UserId
     * @param orderId
     * @return
     */
    @Override
    public Integer getUserIdByOrderId(Long orderId) {
        return voucherMapper.getUserIdByOrderId(orderId);
    }

    @Override
    public void addVoucherOrder(Long voucherId,VoucherOrder voucherOrder) {
        Long userId =voucherOrder.getUserId();
        RLock lock = redisson.getLock("voucher:lock:"+voucherId+":"+userId);
        boolean isLock = lock.tryLock();
        if(!isLock) {
            log.error("重复获取了锁");
            return ;
        }

        try {
            //用代理对象保证事务的特性
            VoucherService proxy = (VoucherService) AopContext.currentProxy();
            proxy.doDBOperation(voucherOrder);
            //给四线交换机发消息，处理过期订单  要求十分钟支付600秒
            rabbitTemplate.convertAndSend("voucher.direct",
                    "voucher.order.delay",
                    voucherOrder
                    );
        } finally {
            lock.unlock();
        }


    }
    @Transactional
    public void doDBOperation(VoucherOrder voucherOrder) {
        // 开启 Redis 事务
        stringRedisTemplate.setEnableTransactionSupport(true);
        stringRedisTemplate.multi();
        try {
            // 幂等性检查  用setnx在redis中判断订单是否已经被消费
            Boolean exists = stringRedisTemplate.opsForValue().setIfAbsent(
                    "voucher:complete:" + voucherOrder.getOrderId(),
                    "",
                    10, TimeUnit.MINUTES
            );
            if (Boolean.TRUE.equals(exists)) {
                return;
            }
            Long voucherId = voucherOrder.getVoucherId();
            //1.扣减库存
            voucherMapper.stockIncreById(voucherId);
            //2.创建订单
            voucherMapper.addVoucherOrder(voucherOrder);
            // 提交 Redis 事务
            stringRedisTemplate.exec();
        }catch (Exception e) {
            // 回滚 Redis 事务
            stringRedisTemplate.discard();
            throw e;
        }
    }

    /**
     * 超时购物券订单处理
     * @param voucherOrder
     */
    @Override
    public void expireVoucherOrderHandler(VoucherOrder voucherOrder) {
        //1.拆包voucherOrder
        Long orderId = voucherOrder.getOrderId();
        //2.幂等性校验
        //2.1查询数据库的voucherOrder状态
        Integer orderStatus = voucherMapper.getOrderStatusById(orderId);
        //2.2如果为未支付（0）就回滚，同时把订单状态改为超时未支付（2）
        if(orderStatus == null||!orderStatus.equals(0)) {
            return ;
        }
        voucherOrder.setStatus(2);
        //3.数据库Redis回滚  使用代理对象防止事务失效
        VoucherService proxy = (VoucherService) AopContext.currentProxy();
        proxy.DBAndRedisRollBack(voucherOrder);


    }

    @Transactional
    public void DBAndRedisRollBack(VoucherOrder voucherOrder) {
        Long orderId = voucherOrder.getOrderId();
        Long voucherId = voucherOrder.getVoucherId();
        Long userId = voucherOrder.getUserId();
        //1.更新数据
        voucherMapper.updateVoucherOrderStatus(orderId, voucherOrder.getStatus());
        if (voucherOrder.getStatus() == 2) {
            voucherMapper.voucherStockRollback(voucherId);
            //2.回滚redis
            //2.1删除一人一单的订单   stringRedisTemplate不支持只删除value，需要调用lua脚本
            stringRedisTemplate.execute(
                    ROLLBACK_SCRIPT,
                    Collections.emptyList(),
                    voucherId.toString(),userId.toString()
            );
        }
        log.info("优惠券超时未支付，已回滚");
    }
}

